package com.web.netty.thread;

import com.web.constant.DataPackageConstants;
import com.web.netty.domain.*;
import com.web.observers.DataObservable;
import com.web.pojo.domain.Device;
import com.web.service.DeviceServer;
import com.web.service.NetworkServer;
import com.web.utils.RedisUtil;
import com.web.utils.ToolUtil;
import loracrypto.en_de_crypto.Cryptor;
import loramic.certify.MicCertifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Author: wws
 * describe:
 */

@Component
public class NettyInThread implements Runnable {

    @Qualifier("getInQueue")
    @Autowired
    private LinkedBlockingQueue<byte[]> inQueue;



    @Qualifier("getOutQueue")
    @Autowired
    public LinkedBlockingQueue<byte[]> outQueue;


    @Autowired
    private DeviceServer deviceServer;

    private Thread thread;

    @Qualifier("getGateWayBridgeResponseObservable")
    @Autowired
    private DataObservable gateWayBridgeResponseObservable;

    @Qualifier("getDeviceResponseObservable")
    @Autowired
    private DataObservable deviceResponseObservable;

    @Qualifier("getDeviceResultObservable")
    @Autowired
    private DataObservable deviceResultObservable;

    @Autowired
    RedisUtil redisUtil;

    @PostConstruct
    public void init() {
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        for (; ; ) {
            try {
                byte[] data = inQueue.take();
                switch (data[0]) {
                    case DataPackageConstants.GATEWAY_REGISTERED_OR_DELETE:
                    case DataPackageConstants.DEVICE_REGISTERED_OR_DELETE:
                    case DataPackageConstants.DEVICE_GROUP:
                    case DataPackageConstants.DATA_DOWNLINK:
                        getGateWayResponse(data);
                        break;
                    case DataPackageConstants.DEVICE_JOIN:
                        deviceJoin(data);
                        break;
                    case DataPackageConstants.DEVICE_DATA_UP:
                        deviceDataUp(data);
                        break;
                    case DataPackageConstants.GET_GATEWAY_HEARTBEAT:
                        System.out.println("GET_GATEWAY_HEARTBEAT");
                        break;
                    case DataPackageConstants.GET_GATEWAY_BRIDGE_HEARTBEAT:
                        System.out.println("GET_GATEWAY_BRIDGE_HEARTBEAT");
                        break;
                    case DataPackageConstants.GET_DEVICE_RXPK:
                        System.out.println("GET_DEVICE_RXPK");
                        break;
                    default:
                        break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理设备上传数据
     * @param data 数据
     */
    private void deviceDataUp(byte[] data) {

        DataUp dataUp = new DataUp(data);
        if (dataUp.getDirection() != DataPackageConstants.UP_LINK) {
            return;
        }
        deviceDataPayloadProcessing(dataUp.getDataUpPayload());

    }

    /**
     * 对设备上传的数据进行MIC的 效验
     * @param dataUpPayload 载荷
     */
    private void deviceDataPayloadProcessing(byte[] dataUpPayload) {
        DataUpPayload payload = new DataUpPayload(dataUpPayload);
        System.out.println("dataUpPayload");
        show(dataUpPayload);
        String Addr = ToolUtil.printHexBinary(payload.getDeviceAddr());
        Device device = deviceServer.selectDeviceByDeviceAddr(Addr);

        if (!MicCertifier.upMsgCertify(dataUpPayload, ToolUtil.toByteArray(device.getDeviceNwkskey()), 4)) {
            System.out.println("MIC错误");
            return;
        }
        byte[] dataFrame = Cryptor.upMsgDeCrypto(dataUpPayload, ToolUtil.toByteArray(device.getDeviceAppskey()));
        System.out.println("dataFrame");
        show(dataFrame);
        dataFrameProcessing(dataFrame);
    }


    /**
     * 数据帧处理
     * @param dataFrame
     */
    private void dataFrameProcessing(byte[] dataFrame) {
        switch (dataFrame[4]) {
            case DataPackageConstants.SWITCH_OPERATING_SET:
                switchOperatingSetResponse(dataFrame);
                break;
            case DataPackageConstants.SWITCH_OPERATING_GET:
                switchOperatingGet(dataFrame);
                break;
            case DataPackageConstants.SWITCH_OPERATING_REPORT:
                switchOperatingReport(dataFrame);
                break;
            default:
        }
    }

    /**
     * 设备操作响应
     * @param dataFrame 数据帧
     */
    private void switchOperatingSetResponse(byte[] dataFrame) {
        deviceResponseObservable.notifyObservers(Arrays.copyOfRange(dataFrame, 1, 3));
    }

    /**
     * 获取数据
     * @param dataFrame 数据帧
     */
    private void switchOperatingGet(byte[] dataFrame) {
        System.out.println("获取数据");
        switch (dataFrame[0]) {
            case DataPackageConstants.SWITCH_LIGHT_STATUS_RESPONSE:
            case DataPackageConstants.SWITCH_CONTROL_STATUS_RESPONSE:
            case DataPackageConstants.SWITCH_SCHEDULE_RESPONSE:
            case DataPackageConstants.SWITCH_DIMMING_LEVEL_RESPONSE:
            case DataPackageConstants.SWITCH_DEVICE_ATTRIBUTES_RESPONSE:
            case DataPackageConstants.SWITCH_REPORTING_PERIOD_RESPONSE:
            case DataPackageConstants.SWITCH_OPERATING_HOURS_RESPONSE:
            case DataPackageConstants.SWITCH_APPKEY_RESPONSE:
            case DataPackageConstants.SWITCH_GET_INFO_RESPONSE:
                deviceResultObservable.notifyObservers(dataFrame);
                break;
            default:
        }
    }

    /**
     * 数据上报信息
     * @param dataFrame 数据帧
     */
    private void switchOperatingReport(byte[] dataFrame) {
        System.out.println("设备上报");
    }


    private void getGateWayResponse(byte[] data) {
        byte[] reqs = Arrays.copyOfRange(data, 1, 3);
        System.out.println("接收到网关回应");
        gateWayBridgeResponseObservable.notifyObservers(reqs);
    }

    /**
     * 设备入网
     * 1.查询设备EUI是否注册
     * 2.将网关信息绑定给设备
     * 3.根据是否动态入网发送入网数据给下位机
     * @param data 数据
     */
    public void deviceJoin(byte[] data) throws InterruptedException {
        //获取入网数据
        ReJoinData reJoinData = new ReJoinData(data);
        System.out.println(reJoinData);
        //判断eui是否存在
        String deviceEUI = ToolUtil.printHexBinary(reJoinData.getDeviceEui());
        Device device = deviceServer.selectDeviceByDeviceEui(deviceEUI);
        if (device == null) {
            //不存在结束方法
            return;
        }

        byte[] deviceAddrBytes = ToolUtil.toByteArray(device.getDeviceAddr());
        //得到入网载荷中的数据进行NWKSKEY和APPSKEY的计算
        byte[] AppNonce = ToolUtil.CalAppNonce(3);
        byte[] joinAcceptMessage = geyJoinAcceptMessage(reJoinData.getPayLoad(), AppNonce);
        byte[] AppKey = ToolUtil.toByteArray(device.getDeviceAppkey());
        byte[] NwkSKey = CalSessionKey(AppKey, joinAcceptMessage, DataPackageConstants.NWKSKEY_TYPE);
        byte[] AppSKey = CalSessionKey(AppKey, joinAcceptMessage, DataPackageConstants.APPSKEY_TYPE);

//        System.out.println("APPKKKKKKKKKKKKKKKKKKKKKKKKKKK");
////        show(AppKey);
////        show(NwkSKey);
////        show(AppKey);
        //保存网关和NWKSKEY和APPSKEY
        deviceServer.deviceJoin(device.getDeviceId(), NwkSKey, AppSKey, reJoinData.getGatewayEui());

        //生成入网应答数据
        byte[] joinResponse = generateJoinResponse(AppNonce, deviceAddrBytes);
        byte[] downPayLoad = generateFinallyJoinResponse(AppKey, joinResponse);
        byte[] downBytes = CalDownLinkData(DataPackageConstants.DYNAMIC_FREQ, deviceAddrBytes, downPayLoad);

        System.out.println("downPayLoad:::::::::::::::::::::");
        show(downPayLoad);
        redisUtil.deleteKey(device.getDeviceAddr());
        outQueue.put(downBytes);
    }


    public static void show(byte[] b) {
        for (int i = 0; i < b.length; i++) {
            System.out.printf("%02x ", b[i]);
        }
        System.out.println();
    }

    public byte[] CalDownLinkData(byte type, byte[] deviceAddr, byte[] payload) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(26);
        byteBuffer.put(DataPackageConstants.DATA_DOWNLINK);
        byteBuffer.put(ToolUtil.CalAppNonce(2));
        byteBuffer.put(DataPackageConstants.DOWN_LINK);
        byteBuffer.put(type);
        byteBuffer.put(deviceAddr);
        byteBuffer.put(payload);
        return byteBuffer.array();
    }

    /**
     * 生成携带mic的入网应答数据
     * @param appkey 设备的appkey
     * @param joinResponse  入网应答数据
     * @return 结果
     */
    public byte[] generateFinallyJoinResponse(byte[] appkey, byte[] joinResponse) {
        //1.将根据入网应答数据数据生成MIC
        byte[] mic = MicCertifier.deviceJoinResponseMic(joinResponse, appkey, 4);
        //2.将入网数据与生成的MIC拼接
        byte[] data = new byte[joinResponse.length + mic.length];
        System.arraycopy(joinResponse, 0, data, 0, joinResponse.length);
        System.arraycopy(mic, 0, data, joinResponse.length, mic.length);
        byte[] cryptoPld = Arrays.copyOfRange(data, 1, data.length);
        byte[] cryptoResPld = Cryptor.deviceJoinResponesCrypto(cryptoPld, appkey);
        byte[] downMsg = new byte[17];
        downMsg[0] = DataPackageConstants.JOIN_ACCEPT_MHDR;
        System.arraycopy(cryptoResPld, 0, downMsg, 1, cryptoResPld.length);
        return downMsg;
    }

    /**
     * 生成初始入网应答数据
     * @param AppNonce 入网时携带的随机数
     * @param DevAddr 给设备生成的DevAddr
     * @return 结果
     */
    private byte[] generateJoinResponse(byte[] AppNonce, byte[] DevAddr) {
        byte[] JoinResponse = new byte[13];
        JoinResponse[0] = DataPackageConstants.JOIN_ACCEPT_MHDR;
        System.arraycopy(AppNonce, 0, JoinResponse, 1, AppNonce.length);
        byte[] NetID = DataPackageConstants.NET_ID;
        System.arraycopy(NetID, 0, JoinResponse, 4, NetID.length);
        System.arraycopy(DevAddr, 0, JoinResponse, 7, DevAddr.length);
        JoinResponse[11] = DataPackageConstants.DLSETTINGS;
        JoinResponse[12] = DataPackageConstants.RXDELAY;
        System.out.println("JoinResponse::::::::::::::::::::::::::::::::::::");
        show(JoinResponse);
        return JoinResponse;
    }


    /**
     * 获取数据中的Join-accept message,见LoRaWAN102-20161012_1398_1（新） #6.2.5 Join-accept message
     * @param reJoinPayload 入网设备携带的数据
     * @return 结果
     */
    private byte[] geyJoinAcceptMessage(ReJoinPayload reJoinPayload, byte[] AppNonce) {
        byte[] payload = new byte[8];
        byte[] NetID = DataPackageConstants.NET_ID;
        byte[] DevNonce = reJoinPayload.getDevNonce();
        System.arraycopy(AppNonce, 0, payload, 0, AppNonce.length);
        System.arraycopy(NetID, 0, payload, 3, NetID.length);
        System.arraycopy(DevNonce, 0, payload, 6, DevNonce.length);
        return payload;
    }

    /**
     * 生成APPSKEY 或 NWKSKEY
     * @param Appkey 初始秘钥
     * @param payload 设备携带的加入接受消息
     * @param keyType 生成秘钥类型 （0x01为NWKSKEY，0x02为APPSKEY）
     * @return
     */
    public byte[] CalSessionKey(byte[] Appkey, byte[] payload, byte keyType) {
        byte[] b = new byte[16];
        b[0] = (byte) keyType;
        System.arraycopy(payload, 0, b, 1, payload.length);
        byte[] result;

        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
            SecretKeySpec key1 = new SecretKeySpec(Appkey, "AES");
            cipher.init(1, key1);
            result = cipher.doFinal(b);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


//    private boolean checkMIC(byte[] bytes){
//        byte[] mic = Arrays.copyOfRange(bytes, 19, 22);
//        byte[] data = Arrays.copyOfRange(bytes,1,19);
//
//        return false;
//    }


}
